Async Value topic
Async Value
To represent loading and error states while fetching data, Refena provides the AsyncValue class.
An AsyncValue can be in 3 states:
AsyncValue.loading: the data is currently being fetchedAsyncValue.error: an error occurred while fetching the dataAsyncValue.data: the data was successfully fetched
final booksProvider = FutureProvider<List<String>>((ref) async {
final response = await http.get(
Uri.https('https://api.books.com/books'),
);
final json = jsonDecode(response.body);
return (json['books'] as List).cast<String>();
});
class Home extends StatelessWidget {
@override
Widget build(BuildContext context) {
final books = context.ref.watch(booksProvider);
// Perform a switch-case on the result to handle loading/error states
return boredSuggestion.when(
data: (books) => Text(data.join(', ')),
loading: () => Text('loading'),
error: (error, stackTrace) => Text('error: $error'),
);
}
}
It comes with neat features such as
whento handle loading/error states,jointo join multipleAsyncValues into one, andmapto map the data type ofAsyncValuewhile keeping the error / loading state.
Transforming the data type of AsyncValue
Let's assume you write a view mode on top of booksProvider that also exposes the number of books:
class BookPageVm {
final int bookCount;
final List<String> books;
BookPageVm({
required this.bookCount,
required this.books,
});
}
final bookPageVmProvider = ViewProvider<AsyncValue<BookPageVm>>((ref) {
final booksAsync = ref.watch(booksProvider);
return booksAsync.map((books) {
return BookPageVm(
bookCount: books.length,
books: books,
);
});
});
As you can see, bookPageVmProvider is a ViewProvider<AsyncValue<BookPageVm>> that depends on booksProvider.
Instead of manually mapping each state of booksAsync,
you can use map to map the data type of booksAsync while keeping the error / loading state.
In the widget, you can then use when to handle the loading/error states (as usual):
class Home extends StatelessWidget {
@override
Widget build(BuildContext context) {
final vm = context.ref.watch(bookPageVmProvider);
// Perform a switch-case on the result to handle loading/error states
return vm.when(
data: (books) => Text('${books.bookCount} books: ${books.books.join(', ')}'),
loading: () => Text('loading'),
error: (error, stackTrace) => Text('error: $error'),
);
}
}
Joining multiple AsyncValues into one
Joining multiple AsyncValues into one is similar to map, but it allows you to join multiple AsyncValues into one.
Here, we use the new Records feature of Dart.
Let's extend the previous example to also fetch the sold-out books:
final soldOutBooksProvider = FutureProvider<List<String>>((ref) async {
final response = await http.get(
Uri.https('https://api.books.com/sold-out'),
);
final json = jsonDecode(response.body);
return (json['books'] as List).cast<String>();
});
We then fetch all books and sold-out books in parallel:
class BookData {
final String title;
final bool soldOut;
BookData({
required this.title,
required this.soldOut,
});
}
final soldOutProvider = ViewProvider<AsyncValue<List<BookData>>>((ref) {
final booksAsync = ref.watch(booksProvider);
final soldOutBooksAsync = ref.watch(soldOutBooksProvider);
return (booksAsync, soldOutBooksAsync).join((books, soldOutBooks) {
return books.map((book) {
return BookData(
title: book,
soldOut: soldOutBooks.contains(book),
);
}).toList();
});
});
As you can see, we use join to join booksAsync and soldOutBooksAsync into one AsyncValue.
Classes
-
AsyncData<
T> Async Value - The data of an AsyncValue.
-
AsyncError<
T> Async Value - The error of an AsyncValue.
-
AsyncLoading<
T> Async Value - The loading state of an AsyncValue.
-
AsyncValue<
T> Async Value